/*
As3 Performances Analyzer
Copyright (C) 2011 Ankama Studio

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Contact : seyraud@ankama.com
Project : http://code.google.com/p/as3-performances-analyzer
*/

package providers
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.ProgressEvent;
	import flash.utils.Dictionary;
	import flash.utils.IDataInput;
	import flash.utils.getTimer;
	import flash.utils.setTimeout;
	
	import interfaces.ITimelineData;
	
	import types.Frame;
	import types.FrameAction;
	import types.ProfilingSession;
	
	import utils.StageShare;
	
	[Event(name="progress", type="flash.events.ProgressEvent")]
	[Event(name="complete", type="flash.events.Event")]
	public class FileProvider extends EventDispatcher
	{
		//://////////////////////////////////////////////////////////////
		//:// Privates variables
		//://////////////////////////////////////////////////////////////
		
		private var _frameCount : uint;
		private var _startByteAvaible : uint;
		private var _stringRef : Dictionary = new Dictionary;
		private var _frames : Vector.<ITimelineData>;
		private var _depthRef : Dictionary;
		private var _session : ProfilingSession = new ProfilingSession;
		private var _classRef : Dictionary = new Dictionary;
		private var _readMemoryLog : Boolean;
		private var _lastStartTimestamp : uint = uint.MAX_VALUE;
		private var _lastEndTimestamp : uint = uint.MAX_VALUE;
		private var _lastStartMem : uint = uint.MAX_VALUE;
		private var _maxMsByFrame : uint;
		private var _cancelParsing : Boolean;
		
		//://////////////////////////////////////////////////////////////
		//:// Getter
		//://////////////////////////////////////////////////////////////
		
		public function get frameCount():uint
		{
			return _frameCount;
		}
		
		public function get profilingSession():ProfilingSession
		{
			return _session;
		}
		
		//://////////////////////////////////////////////////////////////
		//:// Public function
		//://////////////////////////////////////////////////////////////
		
		public function FileProvider()
		{
			_maxMsByFrame = 1000 / StageShare.stage.frameRate;
		}
		
		public function parse(data : IDataInput) : void
		{
			_session.fps = data.readInt();
			_session.swf = data.readUTF();
			_session.memoryInfo = data.readBoolean();
			_readMemoryLog = _session.memoryInfo;
			
			_startByteAvaible = data.bytesAvailable;
			parseFrame(data);
		}
		
		public function cancelParsing() : void
		{
			_cancelParsing = true;
		}
		
		//://////////////////////////////////////////////////////////////
		//:// Privates functions
		//://////////////////////////////////////////////////////////////
		
		private var _lastEndFrame : uint;
		private function parseFrame(input : IDataInput) : void
		{
			var timeLeft : int = _maxMsByFrame;
			while(input.bytesAvailable && timeLeft > 0 && !_cancelParsing)
			{
				var startTs : uint = getTimer();
				try{
					++_frameCount;
					
					// Parse string bock
					while(input.bytesAvailable)
					{
						var index : uint = input.readUnsignedInt();
						
						// End of string block
						if(!index) break;
						
						_stringRef[index] = input.readUTF();
					}
					
					var frame : Frame = new Frame;
					var min : uint = uint.MAX_VALUE;
					var max : uint = 0;
					frame.id = frameCount;
					_depthRef = new Dictionary;
					
					// Parse profiling block
					var blockLen : uint = input.readUnsignedInt();
					var actionCount : uint = 0;
					while(input.bytesAvailable)
					{
						var stringIndex : uint = input.readUnsignedInt();
						
						// End of string block
						if(!stringIndex) break;
						
						++actionCount;
						
						// Read call info block
						var r : FrameAction = new FrameAction;
						r.name = _stringRef[stringIndex];
						
						// New start timestamp
						if(input.readBoolean())
							_lastStartTimestamp = input.readUnsignedInt();
						r.start = _lastStartTimestamp;
						
						// If start/end timestamp are the same
						if(input.readBoolean())
						{
							if(input.readBoolean())
								_lastEndTimestamp = input.readUnsignedInt();
							r.end = _lastEndTimestamp;
						}
						else
							r.end = r.start;
						
						// If mem at start if different from the last start mem
						if(input.readBoolean())
							_lastStartMem = input.readUnsignedInt();
						r.startMemory = _lastStartMem;
						
						// If mem is different from start mem
						if(input.readBoolean())
							r.endMemory = input.readUnsignedInt();
						else
							r.endMemory = r.startMemory;
							
						r.depth = input.readShort() + 1;
						
						// If custom info are avaible
						if(input.readBoolean())
							r.customInfo =  _stringRef[input.readUnsignedInt()];
						
						frame.children.push(r);
						
						if(!_depthRef[r.depth])
							_depthRef[r.depth] = new Vector.<ITimelineData>;
						_depthRef[r.depth].push(r);
						
						// Add child if exist
						if(r.depth && _depthRef[r.depth + 1])
						{
							for each(var child : FrameAction in _depthRef[r.depth + 1])
							{
								child.parent = r;
								r.childrenDuration += child.end - child.start;
								r.childrenMemory += child.endMemory - child.startMemory;
							}
							r.children = _depthRef[r.depth + 1];
							_depthRef[r.depth + 1] = null;
						}
						
						// Store stats about class etc.
						if(!_classRef[r.name])
						{
							_classRef[r.name] = new ClassData;
							with(_classRef[r.name]){
								name			= r.name;
								totalCall		= 1;
								totalTime		= r.end - r.start;
								totalMem		= r.endMemory - r.startMemory;
								totalSelfTime	= r.end - r.start - r.childrenDuration;
								totalSelfMem	= r.endMemory - r.startMemory - r.childrenMemory};
						}
						else
						{
							++ClassData(_classRef[r.name]).totalCall;
							ClassData(_classRef[r.name]).totalTime		+= r.end - r.start;
							ClassData(_classRef[r.name]).totalMem		+= r.endMemory - r.startMemory;
							ClassData(_classRef[r.name]).totalSelfTime	+= r.end - r.start - r.childrenDuration;
							ClassData(_classRef[r.name]).totalSelfMem	+=  r.endMemory - r.startMemory - r.childrenMemory;
						}
						
						if(r.start < min)
						{
							if(_lastEndFrame > r.start) 
								trace("Time issue at frame #" + frameCount);
							else
								min = r.start;
						}
	
						if(r.end > max) max = r.end;
					}
					
					frame.start = min == uint.MAX_VALUE ? 0 : min;
					frame.end = max;
					frame.actionCount = actionCount;
					_session.frames.push(frame);
					_lastEndFrame = max;
				}catch(e:Error)
				{
					//Error #2030: End of file was encountered.
					if(e.errorID != 2030)
						throw e;
				}
				
				timeLeft -= getTimer() - startTs;
			}
			
			dispatchEvent(new ProgressEvent(ProgressEvent.PROGRESS, false, false, _startByteAvaible - input.bytesAvailable, _startByteAvaible));
			
			if(input.bytesAvailable && !_cancelParsing)
				setTimeout(parseFrame, 20, input);
			else
			{
				postProcess();
				dispatchEvent(new Event(Event.COMPLETE));
			}
		}
		
		private function postProcess() : void
		{
			var classList : Array = [];
			for each(var c : ClassData in _classRef)
				classList.push(c);
			classList.sortOn("name");
			
			_classRef = null;
			
			var nodeRef : Dictionary = new Dictionary;
			
			for(var i : uint = 0; i < classList.length; ++i)
			{
				var classData : ClassData = classList[i];
				var tmp : Array = classData.name.split("::");
				var packages : Array = tmp[0].split(".");
				var parent : XML = _session.classTree;
				var currentPackage : String = "";
				var currentNode : XML;
				for each (var p : String in packages)
				{
					if(p == "") continue;
					currentPackage += currentPackage.length ? "." + p : p;
					currentNode = nodeRef[currentPackage];
					if(currentNode)
					{
						parent = currentNode;
						continue;
					}
					currentNode = <package/>;
					currentNode.@id = currentPackage;
					currentNode.@name = p;
					nodeRef[currentPackage] = currentNode;
					parent.appendChild(currentNode);
					parent = currentNode;
				}
				
				parent = nodeRef[tmp[0]] ? nodeRef[tmp[0]] : _session.classTree;
				
				var classInfo : Array = tmp[1].split(".");
				currentNode = nodeRef[currentPackage + "::" + classInfo[0]];
				if(!currentNode)
				{
					currentNode = <class/>;
					currentNode.@id = currentPackage + "::" + classInfo[0];
					currentNode.@name = classInfo[0];
					
					nodeRef[currentPackage + "::" + classInfo[0]] = currentNode;
					parent.appendChild(currentNode);
				}
				
				parent = currentNode;
				currentNode = nodeRef[classData.name];
				if(!currentNode)
				{
					currentNode = <function/>;
					currentNode.@id = classData.name; 
					currentNode.@name = classInfo[1];
					currentNode.@callCount = classData.totalCall;
					currentNode.@totalTime = classData.totalTime;
					currentNode.@totalSelfTime = classData.totalSelfTime;
					currentNode.@totalMem = classData.totalMem < 0 ? 0 : int(classData.totalMem / 1024);
					currentNode.@totalSelfMem = classData.totalSelfMem < 0 ? 0 : int(classData.totalSelfMem / 1024);
					nodeRef[classData.name] = currentNode;
					parent.appendChild(currentNode);
				}
			}
		}
	}
}

internal class ClassData
{
	public var name : String;
	public var totalTime : uint;
	public var totalSelfTime : uint;
	public var totalCall : uint;
	public var totalMem : int;
	public var totalSelfMem : int;
}